package com.ubxtech.gateway.filter;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import com.ubxtech.core.context.ContextConstants;
import com.ubxtech.core.context.ContextToken;
import com.ubxtech.core.context.ContextUtil;
import com.ubxtech.core.dto.AuthHeaderDTO;
import com.ubxtech.core.exception.ErrorCodeCore;
import com.ubxtech.core.utils.AntPathMatcherUtil;
import com.ubxtech.core.utils.StrPool;
import com.ubxtech.gateway.auth.GatewayLoginAuthService;
import com.ubxtech.gateway.properties.CustomGatewayProperties;
import com.ubxtech.gateway.util.ResponseUtil;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import jakarta.annotation.Resource;
import java.util.List;
import java.util.Set;

/**
 * @author Victor.Xiao
 * @since 2022-01-25 16:45
 **/
@Slf4j
@Component
public class TokenFilter implements GlobalFilter , Ordered {

    @Resource
    private CustomGatewayProperties customGatewayProperties;
    @Autowired(required = false)
    private GatewayLoginAuthService gatewayLoginAuthService;

    private List<String> baseUri = CollUtil.newArrayList(
            "/**/*.css",
            "/**/*.js",
            "/**/*.html",
            "/**/*.ico",
            "/**/*.jpg",
            "/**/*.jpeg",
            "/**/*.png",
            "/**/*.gif",
            "/image/**",
            "/fonts/**",
            "/libs/**",
            "/plugins/**",
            "/swagger/**",
            "/**/api-docs/**",
            "/**/api-docs-ext/**",
            "/**/swagger-resources/**",
            "/**/webjars/**",
            "/actuator/**",
            "/**/static/**",
            "/**/public/**",
            "/**/sys/login",
            "/error",
            "/websocket/**"
    );


    public boolean isIgnoreToken(String path) {
        boolean result = AntPathMatcherUtil.match(path,baseUri);
        if (!result) {
            if (CollectionUtil.isNotEmpty(customGatewayProperties.getExcludePath())) {
                result = AntPathMatcherUtil.match(path,customGatewayProperties.getExcludePath());
            }
        }
        return result;
    }

    /**
     * 是否校验某个服务
     * @author Victor.Xiao
     * @since 2023-09-07 16:10
     * @param exchange exchange
     * @return boolean
    */
    public boolean isIgnoreService(ServerWebExchange exchange) {
        // 获取路由信息
        Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
        String serviceId = "";
        if (route != null) {
            serviceId = route.getId();
        }
        if (CollectionUtil.isNotEmpty(customGatewayProperties.getExcludeServiceId()) && StrUtil.isNotBlank(serviceId)) {
            return customGatewayProperties.getExcludeServiceId().contains(serviceId);
        }
        return false;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        if (customGatewayProperties.getAuthFlag() == 0) {
            return chain.filter(exchange);
        }
        ServerHttpRequest serverHttpRequest = exchange.getRequest();
        ServerHttpRequest.Builder mutate = serverHttpRequest.mutate();
        HttpHeaders httpHeaders = serverHttpRequest.getHeaders();
        String token = httpHeaders.getFirst(ContextConstants.TOKEN);
        String appId = httpHeaders.getFirst(ContextConstants.HEADER_APP_ID);
        String deviceId = httpHeaders.getFirst(ContextConstants.HEADER_DEVICE_ID);
        String sign = httpHeaders.getFirst(ContextConstants.HEADER_SIGN);
        if (StrUtil.isBlank(token)) {
            token = exchange.getRequest().getQueryParams().getFirst(ContextConstants.TOKEN);
        }
        AuthHeaderDTO authHeaderDTO = new AuthHeaderDTO();
        authHeaderDTO.setToken(token);
        authHeaderDTO.setAppId(appId);
        authHeaderDTO.setDeviceId(deviceId);
        authHeaderDTO.setSign(sign);
        String serviceId = "";
        String path = "";
        // 获取路由信息
        Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
        if (route != null) {
            serviceId = route.getId();
            path = serverHttpRequest.getPath().pathWithinApplication().value();
            String uriStr = route.getUri().toString();
            if (StrUtil.isNotBlank(uriStr) && uriStr.startsWith("lb:") && !uriStr.contains("http")) {
                serviceId = uriStr.replaceAll("lb://","");
            }
        }
        authHeaderDTO.setPath(path);
        authHeaderDTO.setServiceId(serviceId);

        if (log.isDebugEnabled()) {
            log.debug("网关 token filter headers :{}", authHeaderDTO);
        }
        if (gatewayLoginAuthService != null) {
            //判断是否是内部api
            Set<String> innerApiList = gatewayLoginAuthService.getInnerApiList(serviceId);
            if (CollectionUtil.isNotEmpty(innerApiList)) {
                for (String api : innerApiList) {
                    if (path.equalsIgnoreCase(api)) {
                        //内部api不允许请求
                        return ResponseUtil.write(exchange.getResponse(), ErrorCodeCore.FORBIDDEN);
                    }
                }
            }
        }

        if (!isIgnoreService(exchange) && !isIgnoreToken(serverHttpRequest.getPath().toString())) {
            //如果要鉴权,token和appId就必须有值
            if (StrUtil.isBlank(token) && (StrUtil.isBlank(appId) || StrUtil.isBlank(deviceId) || StrUtil.isBlank(sign))) {
                log.error("鉴权参数为空:{}", authHeaderDTO);
                return ResponseUtil.write(exchange.getResponse(), ErrorCodeCore.UNAUTHORIZED);
            }
            if (gatewayLoginAuthService == null) {
                return ResponseUtil.write(exchange.getResponse(), ErrorCodeCore.UNAUTHORIZED);
            }
            ContextToken contextToken = gatewayLoginAuthService.auth(authHeaderDTO);
            if (contextToken == null || contextToken.getUserId() == null || StrUtil.isBlank(contextToken.getUserId()) || StrPool.ZERO.equalsIgnoreCase(contextToken.getUserId())) {
                log.error("请求结果鉴权不通过:{}", contextToken);
                return ResponseUtil.write(exchange.getResponse(), ErrorCodeCore.UNAUTHORIZED);
            }
            //网关接口鉴权
            if (customGatewayProperties.getPermissionFlag() == 1) {
                // 获取路由信息
                if (route != null) {
                    //交给实现类处理
                    if (!gatewayLoginAuthService.hasApiPermission(serviceId, path, contextToken.getPermissions())) {
                        return ResponseUtil.write(exchange.getResponse(), ErrorCodeCore.FORBIDDEN);
                    }
                }
            }
            addHeader(contextToken, mutate);
            exchange = exchange.mutate().request(mutate.build()).build();
        } else {
            //如果是忽略的url,而且token不为空,也去请求一次auth接口,如果有效则添加header,无效则继续
            if (StrUtil.isNotBlank(token)) {
                ContextToken contextToken = gatewayLoginAuthService.auth(authHeaderDTO);
                addHeader(contextToken, mutate);
            }
        }

        return chain.filter(exchange);
    }

    private void addHeader(ContextToken contextToken,ServerHttpRequest.Builder mutate) {
        if (contextToken == null) {
            return;
        }
        if (StrUtil.isNotBlank(contextToken.getUserId())) {
            MDC.put(ContextConstants.USER_ID, contextToken.getUserId());
            addHeader(mutate,ContextConstants.USER_ID, String.valueOf(contextToken.getUserId()));
        }
        if (StrUtil.isNotBlank(contextToken.getNickname())) {
            addHeader(mutate,ContextConstants.NICKNAME, contextToken.getNickname());
        }
        if (StrUtil.isNotBlank(contextToken.getToken())) {
            //重新添加一次是如果token放在url中的,后面可能就拿不到
            addHeader(mutate,ContextConstants.TOKEN, contextToken.getToken());
        }
        if (StrUtil.isNotBlank(contextToken.getUsername())) {
            addHeader(mutate,ContextConstants.USERNAME, contextToken.getUsername());
        }
        if (StrUtil.isNotBlank(contextToken.getTenant())) {
            addHeader(mutate,ContextConstants.KEY_TENANT, contextToken.getTenant());
        }
        if (CollectionUtil.isNotEmpty(contextToken.getTenantMulti())) {
            //信息可能会很长,需要设置server.max-http-header-size=102400 默认header长度为8KB
            addHeader(mutate, ContextConstants.KEY_TENANT_MULTI, StrUtil.join(",",contextToken.getTenantMulti()));
        }
//          //信息可能会很长,需要设置server.max-http-header-size=102400 默认header长度为8KB
        if (CollectionUtil.isNotEmpty(contextToken.getPermissions())) {
            addHeader(mutate,ContextConstants.HEADER_PERMISSION, StrUtil.join(",",contextToken.getPermissions()));
        }
        if (CollectionUtil.isNotEmpty(contextToken.getAreaList())) {
            addHeader(mutate,ContextConstants.HEADER_AREA_LIST, StrUtil.join(",",contextToken.getAreaList()));
        }
        if (contextToken.getAdminFlag() != null) {
            addHeader(mutate, ContextConstants.HEADER_ADMIN_FLAG, contextToken.getAdminFlag());
        }

    }

    private void addHeader(ServerHttpRequest.Builder mutate, String name, Object value)
    {
        if (StrUtil.isBlank(name) || value == null || StrUtil.isBlank(value.toString())) {
            return;
        }
        String valueStr = value.toString();
        String valueEncode = URLUtil.encode(valueStr);
        mutate.header(name, valueEncode);
    }

    @Override
    public int getOrder() {
//        return Ordered.LOWEST_PRECEDENCE;
        //需要在load balancer之前处理,好拿到token等信息
        return ReactiveLoadBalancerClientFilter.LOAD_BALANCER_CLIENT_FILTER_ORDER-1;
    }
}
